本文旨在指导大家如何通过使用Tab Control控件实现选项卡功能,构建整个读写器上位机软件的主要框架。在这个过程,大家将会初步接触并了解:
+1. 什么是基于对话框的MFC程序;
+2. Tab Control控件如何使用;
+3. Windows程序的消息处理机制。
首先应该清楚的是,我们的读写器上位机软件是一个基于对话框的应用程序,使用VC++开发的MFC应用程序类型有以下三种单文档、多文档与基于对话框(摘自《MFC应用程序类型:单文档、多文档与基于对话框》)。1
2
3
4
5
6
7
8
9
10
11
12
13
14单文档:记事本类程序的标准模式,有菜单栏、工具栏等,只能进行一份文档的操作,即不能同时在同一个应用程序中打开两个文件;
多文档:WORD类或浏览器程序的标准模式,可多个窗口显示不同的信息,进行不同的任务,有过个视图环境,可同时操作多个文件。
多个文件共享同一菜单栏、工具栏等;
对话框:可方便的使用控件,所见即所得的编程,没有菜单栏、工具栏等;
怎样看出一个MFC应用程序是基于单文档、多文档还是对话框?
1.运行应用程序观看,对话框一般没有菜单栏、工具栏等,单文档和多文档有;
另外多文档可产生子窗口,会有一个childframe class.
2.从源码中的类观看:
基于对话框—>一般含以下3个类:CAboutDlg、程序名App、程序名Dlg;
基于单文档—>一般含以下4个类:CMainFrame、程序名App、程序名Doc、程序名View;
基于多文档—>一般含以下5个类:CMainFrame、CChildFrame、程序名App、程序名Doc、程序名View.
第1步: 创建基于对话框的应用程序
所以,首先,我们要创建一个基于对话框的MFC应用程序。右上角File->New弹出以下对话框,选择应用程序的类型是MFC AppWizard(.exe),输入工程名,选择工程存放路径,点击OK进入创建向导。最关键的一步是类型一定要选择Dialog based(基于对话框),然后就可以Finish或者你也可以Next下去看有什么其他信息然后自定义进行修改,最后Finish,这样就创建了一个基于对话框的应用程序了。你可以执行一发看看效果。
接下来我们看一下通过上述操作,VC++悄悄帮我们做了哪些不可告人的事情。正如上面所说的,通过上述操作后自动生成了三个类(如下图),其中“C工程名App”可以理解为整个应用程序的主体框架,由她去启动我们的主对话框“C工程名Dlg”。“CAboutDlg”是帮助对话框,运行程序后弹出主对话框,点击主对话框左上角小图标会弹出一个下拉菜单,点击“关于….”后弹出的对话框即是帮助对话框的内容。这里需要指出的是,MFC的编程惯例是类名都以C开头(ClassView视图查看类信息),而该类相关的头文件、源文件(FileView视图查看文件信息)文件名则以去掉C之后的部分命名。
第2步: 添加TabControl控件
把工具自动生成的控件(下图所示的按钮、静态文本)一一选中后Delete掉。然后点击右边工具栏中的Tab Control控件将其拖拽到主对话框上。
调整Tab Control控件大小,使其布满整个主对话框(如下图所示)。选中Tab Control控件右键->Properties可以对控件的属性进行设置,需要注意的属性有:
General下的控件ID(后面需要在代码中通过控件ID绑定控件变量进而达到控制控件的目的);
Styles下的N 对齐;
More Styles下的B 底部(界面设计第二种参考方案通过修改这个属性达到选项卡切换条在内容区下方的效果)。
第3步: 为TabControl控件绑定成员变量
选中控件->右键->ClassWizard弹出类向导对话框(如下图所示)。其中Messages Maps用于创建消息响应函数,例如点击某个按钮的响应函数;Member Variables则是类成员变量信息,主要是一些控件与其映射的类成员变量信息。我们选中Member Variables,选中IDC_TAB1(这个ID正是上述Tab Control控件对应的ID),点击Add Variable…输入变量名进行添加。注意这里该变量是一个Control类别,CTabCtrl类型的变量,这些内容后续控件部分会进一步深入说明。另外,MFC编程惯例中成员变量要以m_开头。
最后OK确认后,我们看到在主对话框类声明中多了一个public成员变量,另外,在该类源文件中的DoDataExchange函数里面通过DDX_Control(pDX, IDC_TAB1, m_xxx)将变量与控件ID绑定,最终达到通过成员变量来控制控件的目的。为我们后面通过代码控制该控件实现选项卡功能做好准备。
注意:上面我们的控件对应的成员变量名是
m_MainFrame
,Tab Control控件的ID是IDC_TAB1
,这些你都可以进行自定义修改,修改时ID一般字母全部大写,变量名以m_
开头,另外请确保变量名具有可辨性。
第4步: 在主对话框的OnInitDialog中初始化选项卡
OnInitDialog函数是主对话框在显示之前调用的,用于对主对话框进行初始化设置,所以我们在其中对新添加的Tab Control控件进行初始化。想想实现一个选项卡功能,无非要解决以下问题:
+1. 选项卡上面要显示几个标签,也就是几个选项条?
+2. 每个标签怎么和对应的内容关联起来?
+3. 选项卡的切换效果怎么实现的?
前面2个问题将在OnInitDialog函数中解决,最后1个问题将在最后1步中讲述。(注意,下面代码为了方便显示内容,并不按照C++规范,因此你需要仔细阅读#
后面的说明内容,然后再将关键部分代码Copy到你的工程;另外,部分MFC API有做说明,你可以通过MSDN获取更多信息。详细的代码分析会在本节后面内容讲述。)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30#/********************** SetIcon一行代码之后 **********************/
# 1. 插入Tab选项卡标签
# m_MainMenu就是上述创建的TabControl控件对应的变量,注意上一步中的变量名是m_MainFrame!
m_MainMenu.InsertItem(0, "标签名"); # InsertItem(标签下标,标签名)
# 2. 关联对话框,将TAB控件设为选项卡对应对话框的父窗口
# m_MenuDebugger是调试助手对话框对应的变量
# Create(m_MenuDebugger变量对应的对话框ID, GetDlgItem(Tab Control控件对应ID));
m_MenuDebugger.Create(IDD_DEBUGGER, GetDlgItem(IDC_TAB));
# 3. 获取TAB控件客户区大小,用于调整选项卡对话框在父窗口中的位置
CRect rect;
m_MainMenu.GetClientRect(&rect);
# 原点位于左上角,X轴Y轴分别指向右方和下方
# 可以理解为距离上边框28px;左、右边框5px;下边框5px,具体数值请自己调试、美化!
rect.top += 28;
rect.right -= 5;
rect.bottom -= 5;
rect.left += 5;
# 4. 设置子对话框尺寸并移动到指定位置
m_MenuDebugger.MoveWindow(&rect);
# 5. 设置默认选项卡,对选项卡对话框进行隐藏和显示
m_MenuDebugger.ShowWindow(SW_SHOWNORMAL或SW_HIDE); # 为SW_SHOWNORMAL设置显示窗口;为SW_HIDE设置隐藏窗口
m_MainMenu.SetCurSel(0); # 为Tab Control控件设置显示窗口对应的下标,一般为:0
#/********************** return TRUE返回之前 **********************/
这里我们将每个标签对应的内容通过对话框呈现,这样只要我们将每个标签对应的对话框设计好,然后通过控制对话款的显示与隐藏对选项卡的切换做出响应,便能实现选项卡的功能了。不过前提是,我们需要先把每个标签对应的对话框设计好(这个通过使用基本控件可以实现,下节课将重点介绍),并且将其关联到主对话框成员变量上,进而通过关联的成员变量控制对话框。所以在插入上述代码之前,我们需要为我们的每一个标签准备一个对话框。
ResourceView->Dialog右键->Insert Dialog插入新的对话框资源。(对对话框进行界面设计部分我们下一节再介绍)。新建好对话框之后我们需要对其属性进行设置,使其能达到实现我们功能的目的。选中对话框右键->Properties->弹出下方属性设置弹框:
给对话框起一个容易辨识的ID(这里IDD_DEBUGGER是调试助手标签对应对话框的ID);
Styles中Styles改为Child(下层),Border改为None(无);
另外一个值得关注的属性是General下的字体。
创建好一个标签对应的对话框后,我们需要为其创建对应的类。右键对话框->classWizard->便会提示您是否新建与该对话框对应的类->是之后便出现下面界面。类名建议符合惯例以C开头,类名最好能辨识关联的对话框,这里CDebugger类对应调试助手。另外需要留意的是Base class还有Dialog ID等信息,这里的基类是MFC API中的CDialog类,ID正是我们刚新建对话框的ID。
为新建的对话框创建好对应类之后,我们就可以在主对话框中添加该新建对话框类型的成员变量了。在ClassView中选中主对话框类(C工程名Dlg类)->右键->Add Member Variables。注意(见下图)Variable Type要与新建的对话框对应类保持一致,变量名最好符合惯例以m_开头而且能够辨识,最好是私有成员变量。
(这些就是C++的一些知识了,大家可以自己摸索,你也可以直接在主对话框类的头文件中直接添加以下声明,再将CDebugger类的头文件声明Debugger.hinclude进来,效果完全一样,通过Add Member Variables的方式实际上也只是通过工具自动生成如下声明,完成include操作)。
1 | # 主对话框类其他声明 |
到这里,应该就能弄明白本节开头提供的代码中m_MenuDebugger.Create(IDD_DEBUGGER, GetDlgItem(IDC_TAB))一句的作用,即通过Create方法(MFC自带API,可以借助MSDN查找),让成员变量与对应的对话框绑定,进而对其进行控制。
其他内容较为简单,请参见本节开头代码及里面的详细注释,这里不再赘述。
第5步: 主对话框中添加处理选项卡切换的响应函数
到这一步,我们已经完成选项卡的大部分功能,只是当选项卡切换时,其标签对应的内容并没有切换。这一块的实现需要涉及MFC程序的消息机制,这里就先举个例子给大家介绍一下,更为深入的内容后续课程会向大家讲解。比如我们点击界面上某个按钮,操作系统能够监听到我们鼠标的行为,会产生一个点击事件,并且将该事件与被点击的按钮绑定起来,然后将这个消息放入一个系统维护的消息队列中;有消息的产生必然需要对消息进行处理,应用程序会通过一个消息循环不断地从消息队列中取出消息,并做出响应。
在实际的MFC程序中,已经将上述过程的绝大部分进行封装,我们开发需要做的只是确定消息类型并为该消息编写响应函数即可(这一块后续也会进一步讲解)。
选中Tab Control控件右键->Class Wizard->选中该控件ID(这里是IDC_TAB1),消息类型选择TCN_SELCHANGE(也就是控件标签改变事件),然后Add Function添加消息响应函数OnSelchangeTab,然后Edit Code编辑该响应函数。
这样一来,当我们通过鼠标点击切换选项卡内容时,响应函数就会被调用,我们只要在其中完成响应标签对话框的显示与隐藏即能实现选项卡切换的效果。
1 | # 6. 处理选项卡切换 |
到这里,我们已经能够实现选项卡的功能,整个流程也已经给大家详细介绍了。最后还想和大家分享的是,通过上述方式添加TCN_SELCHANGE消息响应函数OnSelchangeTab后,工具自动生成的代码主要有两部分(见下)。
第一,类声明中增加了以afx_msg修饰的保护类型的响应函数声明;
第二,源文件中,在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间通过ON_NOTIFY(自带API)将消息,控件ID,响应函数关联起来。
CxxxDlg.h头文件1
2
3
4
5
6
7
8
9
10
11
12
13# 主对话框其他声明....
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
//{{AFX_MSG(CxxxDlg)
afx_msg void OnSelchangeTab(NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
# 主对话框其他声明....
CxxxDlg.cpp源文件1
2
3
4
5BEGIN_MESSAGE_MAP(CxxxDlg, CDialog)
//{{AFX_MSG_MAP(CxxxDlg)
ON_NOTIFY(TCN_SELCHANGE, IDC_TAB, OnSelchangeTab)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
Version Control
版本号 | 日期 | 内容 | 作者 |
V0.1 | 2015.9.29 | 起草博客 | Tarantula-7 |
V0.7 | 2015.10.2 | 添加Create等API说明;强调几处易混淆点 | Tarantula-7 |